package com.demo.dao;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.Version;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.persistence.metamodel.SingularAttribute;
import javax.sql.DataSource;
import javax.annotation.PostConstruct;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.service.jdbc.connections.internal.DatasourceConnectionProviderImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import com.demo.framework.paging.JoinColumn;
import com.demo.framework.paging.JoinParam;
import com.demo.framework.paging.PagingParam;
import com.demo.framework.paging.PagingResult;
import com.demo.framework.repo.DaoUtils;
import com.google.common.reflect.TypeToken;

@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class AbstractDaoSupport<T> {
	protected final Logger logger = LoggerFactory.getLogger(getClass());
	protected JdbcTemplate jdbcTemplate;
	protected NamedParameterJdbcTemplate namedParameterJdbcTemplate;

	// protected SimpleJdbcInsert jdbcInsert;

	@PersistenceContext(unitName = "app-repo")
	protected EntityManager entityManager;

	protected DaoUtils daoUtils;

	protected final Class<T> entityClass;

	public AbstractDaoSupport() {
		// 获取子类的泛型信息
		TypeToken<? extends AbstractDaoSupport> token = TypeToken.of(getClass());
		TypeToken<?> resolveType = token.resolveType(AbstractDaoSupport.class.getTypeParameters()[0]);
		entityClass = (Class<T>) resolveType.getRawType();
	}

	/**
	 * 初始化jdbcTemplate，提供对jdbcTemplate的使用
	 */
	@PostConstruct
	public void init() {
		daoUtils = new DaoUtils(entityManager);
		// 设法取到底层dataSource，来做一些原生的查询
		EntityManagerFactory entityManagerFactory = entityManager.getEntityManagerFactory();
		if (entityManagerFactory instanceof HibernateEntityManagerFactory) {
			SessionFactory sessionFactory = ((HibernateEntityManagerFactory) entityManagerFactory).getSessionFactory();
			if (sessionFactory instanceof SessionFactoryImpl) {
				SessionFactoryImpl factoryImpl = (SessionFactoryImpl) sessionFactory;
				DatasourceConnectionProviderImpl connectionProvider = factoryImpl.getConnectionProvider().unwrap(
						DatasourceConnectionProviderImpl.class);
				DataSource dataSource = connectionProvider.getDataSource();
				this.jdbcTemplate = new JdbcTemplate(dataSource);
				this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
			}
		}
	}

	/**
	 * 获取下载orm.xml里面的查询语句的sql内容， 这样可以使用jdbcTemplate来执行写在orm.xml里面的语句
	 * @param queryName
	 * @return
	 */
	public String getNameQuery(String queryName) {
		Session session = entityManager.unwrap(Session.class);
		Query query = session.getNamedQuery(queryName);
		return query.getQueryString();
	}

	public JdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}

	/**
	 * 
	 * @param entityClass 要返回的表对应的类型
	 * @return 表的所有内容
	 */
	public <E> List<E> findAll(final Class<E> entityClass) {
		CriteriaQuery<E> query = entityManager.getCriteriaBuilder().createQuery(entityClass);
		query.select(query.from(entityClass));
		return entityManager.createQuery(query).getResultList();
	}

	/**
	 * 根据条件查询当前dao对应表的内容，表由当前dao类继承的时候指定。
	 * @param pagingParam  查询条件 ，包含过滤，排序及分页
	 * @return 分页结果，包含总数
	 */
	public PagingResult<T> findPaging(PagingParam pagingParam) {
		List<T> resultList = this.queryList(entityClass, pagingParam);
		long total = this.queryCount(entityClass, pagingParam);
		PagingResult<T> result = new PagingResult<T>(resultList, (int) total);
		return result;
	}
	/**
	 * 根据条件查询 参数对应表的内容，表由entityClass指定。
	 * @param pagingParam  查询条件 ，包含过滤，排序及分页
	 * @return 分页结果，包含总数
	 */
	public <E> PagingResult<E> findPaging(Class<E> entityClass, PagingParam pagingParam) {
		List<E> resultList = this.queryList(entityClass, pagingParam);
		long total = this.queryCount(entityClass, pagingParam);
		PagingResult<E> result = new PagingResult<E>(resultList, (int) total);
		return result;
	}

	/**
	 * 根据条件查询 参数对应表的内容，表由entityClass指定。
	 * @param pagingParam  查询条件 ，包含过滤，排序及分页
	 * @return 分页结果数组
	 */
	public <E> List<E> findList(Class<E> entityClass, PagingParam pagingParam) {
		return queryList(entityClass, pagingParam);
	}
	/**
	 * 根据查询条件查询表的其中一列
	 * @param attribute 要获取的属性
	 * @param pagingParam 查询条件 ，包含过滤，排序及分页
	 * @return 返回一个List，list的值为Entity的某个属性，通过attribute参数指定。
	 */
	public <E,R> List<R> findList(PagingParam pagingParam, SingularAttribute<E, R> attribute) {
		return queryList(pagingParam,attribute,true);
	}
	public <E,R> List<R> findList(PagingParam pagingParam, SingularAttribute<E, R> attribute, boolean distinct) {
		return queryList(pagingParam,attribute, distinct);
	}
	/**
	 * 根据查询条件查询表的几列, 注意，这几个列必须是同一个类的列
	 * @param attributes 要获取的多个属性
	 * @param pagingParam 查询条件 ，包含过滤，排序及分页
	 * @return 返回一个List，list的值为 Object[]，Object[]包含要查询的哪些列的内容
	 */
	public <E> List<Object[]> findList(PagingParam pagingParam, SingularAttribute<E, ?>... attributes  ) {
		return queryList(pagingParam, attributes);
	}
	/**
	 * 当前类内部使用的方法，根据条件查询 参数对应表的内容，表由entityClass指定。
	 * @param pagingParam  查询条件 ，包含过滤，排序及分页
	 * @return 分页结果数组
	 */
	public <E> List<E> queryList(Class<E> entityClass, PagingParam pagingParam) {
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<E> query = builder.createQuery(entityClass);
		Root<E> root = query.from(entityClass);
		List<Predicate> filter = daoUtils.buildFilters(root, pagingParam.getFilter());
		if (pagingParam.getJoinParam() != null) {
			JoinParam<?> joinParam = pagingParam.getJoinParam();
			Root<?> root2 = query.from(joinParam.getJoinClass());
			for (JoinColumn joinColumn : joinParam.getJoins()) {
				filter.add(builder.equal(root.get(joinColumn.getMasterField()), root2.get(joinColumn.getReferField())));
			}
			filter.addAll(daoUtils.buildFilters(root2, joinParam.getFilter()));
		}
		query.where(filter.toArray(new Predicate[filter.size()]));
		List<Order> orders = daoUtils.buildOrders(root, pagingParam.getSort());
		query.select(root).orderBy(orders);
		TypedQuery<E> exeQuery = entityManager.createQuery(query);
		if (pagingParam.getStart() != null && pagingParam.getLimit() != null) {
			exeQuery.setFirstResult(pagingParam.getStart()).setMaxResults(pagingParam.getLimit());
		}
		return exeQuery.getResultList();
	}
	
	/**
	 * 根据查询条件查询表的其中一列
	 * @param attribute 要获取的属性
	 * @param pagingParam 查询条件 ，包含过滤，排序及分页
	 * @return 返回一个List，list的值为Entity的某个属性，通过attribute参数指定。
	 */
	public <E,R> List<R> queryList( PagingParam pagingParam, SingularAttribute<E, R> attribute, boolean distinct) {
		Class<E> entityClass = attribute.getDeclaringType().getJavaType();
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<R> query = builder.createQuery(attribute.getJavaType());
		Root<E> root = query.from(entityClass);
		List<Predicate> filter = daoUtils.buildFilters(root, pagingParam.getFilter());
		if (pagingParam.getJoinParam() != null) {
			JoinParam<?> joinParam = pagingParam.getJoinParam();
			Root<?> root2 = query.from(joinParam.getJoinClass());
			for (JoinColumn joinColumn : joinParam.getJoins()) {
				filter.add(builder.equal(root.get(joinColumn.getMasterField()), root2.get(joinColumn.getReferField())));
			}
			filter.addAll(daoUtils.buildFilters(root2, joinParam.getFilter()));
		}
		query.where(filter.toArray(new Predicate[filter.size()]));
		List<Order> orders = daoUtils.buildOrders(root, pagingParam.getSort());
		query.select(root.get(attribute)).orderBy(orders);
		if(distinct){
			query.distinct(true);
		}
		TypedQuery<R> exeQuery = entityManager.createQuery(query);
		if (pagingParam.getStart() != null && pagingParam.getLimit() != null) {
			exeQuery.setFirstResult(pagingParam.getStart()).setMaxResults(pagingParam.getLimit());
		}
		return exeQuery.getResultList();
	}
	
	/**
	 * 根据查询条件查询表的几列, 注意，这几个列必须是同一个类的列
	 * @param attributes 要获取的多个属性
	 * @param pagingParam 查询条件 ，包含过滤，排序及分页
	 * @return 返回一个List，list的值为 Object[]，Object[]包含要查询的哪些列的内容
	 */
	public <E> List<Object[]> queryList(PagingParam pagingParam, SingularAttribute<E, ?>... attributes  ) {
		Class<E> entityClass = attributes[0].getDeclaringType().getJavaType();
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
		Root<E> root = query.from(entityClass);
		List<Predicate> filter = daoUtils.buildFilters(root, pagingParam.getFilter());
		if (pagingParam.getJoinParam() != null) {
			JoinParam<?> joinParam = pagingParam.getJoinParam();
			Root<?> root2 = query.from(joinParam.getJoinClass());
			for (JoinColumn joinColumn : joinParam.getJoins()) {
				filter.add(builder.equal(root.get(joinColumn.getMasterField()), root2.get(joinColumn.getReferField())));
			}
			filter.addAll(daoUtils.buildFilters(root2, joinParam.getFilter()));
		}
		query.where(filter.toArray(new Predicate[filter.size()]));
		List<Order> orders = daoUtils.buildOrders(root, pagingParam.getSort());
		Selection<?>[] selections = new Selection[attributes.length];
		for (int i = 0; i < attributes.length; i++) {
			selections[i] = root.get(attributes[i]);
		}
		query.multiselect(selections).orderBy(orders);
//		query.select(builder.array(selections)).orderBy(orders);
		TypedQuery<Object[]> exeQuery = entityManager.createQuery(query);
		if (pagingParam.getStart() != null && pagingParam.getLimit() != null) {
			exeQuery.setFirstResult(pagingParam.getStart()).setMaxResults(pagingParam.getLimit());
		}
		return exeQuery.getResultList();
	}

	public <E> long findCount(Class<E> entityClass, PagingParam pagingParam) {
		return queryCount(entityClass, pagingParam);
	}

	public <E> long queryCount(Class<E> entityClass, PagingParam pagingParam) {
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<Long> query = builder.createQuery(Long.class);
		Root<E> root = query.from(entityClass);
		query.select(builder.count(root));
		List<Predicate> filter = daoUtils.buildFilters(root, pagingParam.getFilter());
		if (pagingParam.getJoinParam() != null) {
			JoinParam<?> joinParam = pagingParam.getJoinParam();
			Root<?> root2 = query.from(joinParam.getJoinClass());
			for (JoinColumn joinColumn : joinParam.getJoins()) {
				filter.add(builder.equal(root.get(joinColumn.getMasterField()), root2.get(joinColumn.getReferField())));
			}
			filter.addAll(daoUtils.buildFilters(root2, joinParam.getFilter()));
			query.where(filter.toArray(new Predicate[filter.size()]));
		}
		query.where(filter.toArray(new Predicate[filter.size()]));
		TypedQuery<Long> exeQuery = entityManager.createQuery(query);
		Long count = exeQuery.getSingleResult();
		return count;
	}

	// 现在公用方法太多了，为了降低复杂度，不再提供findXxByCol方法，统统通过pagingparam来实现
	public List<T> findAllByCol(String fieldName, Serializable fieldValue) {
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<T> query = builder.createQuery(entityClass);
		Root<T> root = query.from(entityClass);
		query.select(root).where(builder.equal(root.get(fieldName), fieldValue));
		return entityManager.createQuery(query).getResultList();
	}

	public <E> List<E> findAllByCol(Class<E> entityClass, String fieldName, Serializable fieldValue) {
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<E> query = builder.createQuery(entityClass);
		Root<E> root = query.from(entityClass);
		query.select(root).where(builder.equal(root.get(fieldName), fieldValue));
		return entityManager.createQuery(query).getResultList();
	}

	public T findByCol(String fieldName, Serializable fieldValue) {
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<T> query = builder.createQuery(entityClass);
		Root<T> root = query.from(entityClass);
		query.select(root).where(builder.equal(root.<String> get(fieldName), fieldValue));
		return entityManager.createQuery(query).getSingleResult();
	}

	public <E> E findByCol(Class<E> entityClass, String fieldName, Serializable fieldValue) {
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<E> query = builder.createQuery(entityClass);
		Root<E> root = query.from(entityClass);
		query.select(root).where(builder.equal(root.<String> get(fieldName), fieldValue));
		return entityManager.createQuery(query).getSingleResult();
	}

	public T find(Serializable primaryKey) {
		return entityManager.find(entityClass, primaryKey);
	}

	public <E> E find(Class<E> entityClass, Serializable primaryKey) {
		return entityManager.find(entityClass, primaryKey);
	}

	public void persist(T entity) {
		entityManager.persist(entity);
	}

	public <E> void persist(Class<E> entityClass, Object entity) {
		entityManager.persist(entity);
	}

	public void persist(Collection<T> list) {
		if (list != null) {
			for (T entity : list) {
				persist(entity);
			}
		}
	}

	public T merge(T entity) {
		Field[] fields = entity.getClass().getDeclaredFields();
		try {
			for (Field field : fields) {
				if (field.isAnnotationPresent(Version.class)) {
					Object object = FieldUtils.readField(field, entity, true);
					if (object instanceof Date) {
						Date date = (Date) object;
						// fix
						// hibernate与mysql结合的一个bug，mysql没毫秒，hibernate有毫秒。结果乐观锁校验会出错。
						// 去除entity里面的毫秒数，这样hibernate就和mysql数据库保持一致了,当然这样同一秒内同时修个一个数据无法被乐观锁检测出来。
						// 还好这个秒级场景非常少见
						date.setTime((date.getTime() / 1000) * 1000);
					}
				}
			}
		} catch (IllegalAccessException e) {
			logger.error("fetch version column failed.", e);
			// 获取version列失败，忽略
		}
		T merge = entityManager.merge(entity);
		return merge;
	}

	public List<T> merge(List<T> list) {
		if (list != null) {
			for (int i = 0, ln = list.size(); i < ln; i++) {
				T entity = list.get(i);
				list.set(i, merge(entity));
			}
		}
		return list;
	}

	public <E> void removeById(Class<E> entityClass, Object primaryKey) {
		E ref = entityManager.getReference(entityClass, primaryKey);
		entityManager.remove(ref);
	}

	public void removeById(Object primaryKey) {
		T ref = entityManager.getReference(entityClass, primaryKey);
		entityManager.remove(ref);
	}

	public void remove(Object entity) {		
		entityManager.remove(entity);
	}
}
